package com.qingzhuge.member.service.impl;

import com.google.gson.Gson;
import com.qingzhuge.common.Assert;
import com.qingzhuge.common.HttpCode;
import com.qingzhuge.common.Resources;
import com.qingzhuge.common.enums.EMemberLoginType;
import com.qingzhuge.common.excel.ExceptionUtil;
import com.qingzhuge.common.jwt.JwtTokenUtil;
import com.qingzhuge.common.utils.WebUtil;
import com.qingzhuge.exception.*;
import com.qingzhuge.member.dao.SysMemberDao;
import com.qingzhuge.member.dto.*;
import com.qingzhuge.member.dto.response.SysMemberDto;
import com.qingzhuge.member.entity.SysMember;
import com.qingzhuge.member.entity.SysMemberConfig;
import com.qingzhuge.member.entity.SysMemberThirdPartyAuth;
import com.qingzhuge.member.service.ISysMemberService;
import com.qingzhuge.service.BasePkLongServiceImpl;
import com.qingzhuge.sms.service.ISysSmsService;
import com.qingzhuge.sys.entity.SysConfig;
import com.qingzhuge.sys.entity.SysSiteConfig;
import com.qingzhuge.sys.service.ISysConfigService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.qingzhuge.common.Constants.CURRENT_USER;
import static com.qingzhuge.common.Constants.CURRENT_USER_ID;
import static com.qingzhuge.member.service.impl.SysMemberServiceImpl.CACHE_NAME;

/**
 * @author : zero.xiao
 * @description :
 * @date :2018/6/12 0012 下午 17:25
 * @modified :
 * @since : 1.0.0
 */
@Service
@CacheConfig(cacheNames = CACHE_NAME)
public class SysMemberServiceImpl extends BasePkLongServiceImpl<SysMemberDao, SysMember> implements ISysMemberService {
    static final String CACHE_NAME = "sysMemberService";
    private final static String UID = "uid";
    private final static String ACCOUNT = "account";
    private final static String OPEN_ID = "openId";
    @Resource
    private ISysSmsService smsService;
    @Autowired(required = false)
    private PasswordEncoder passwordEncoder;

    @Resource
    private ISysConfigService configService;

    private SysSiteConfig getSite() {
        return configService.getSiteConfig();
    }

    @Override
    public String encodePassword(String password) {
        if (passwordEncoder == null) {
            return null;
        }
        String encode = passwordEncoder.encode(password);
        log.debug("password:[{}]",encode);
        return encode;
    }

    @Override
    public boolean matchesPassword(String pawPassword, String encodePassword){
        if (passwordEncoder == null) {
            return true;
        }
        return passwordEncoder.matches(pawPassword,encodePassword);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMember save(SysMember user) {
        if (user.isNew()) {
            user.setPassword(encodePassword(user.getPassword()));
        } else {
            user.setPassword(null);
        }
        user = super.save(user);
        return user;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMember updatePassword(Long userId, String password, String newPassword) {
        SysMember user = this.findById(userId);
        if (null != user) {
            if (Objects.equals(user.getPassword(), encodePassword(password))) {
                user.setPassword(encodePassword(newPassword));
                this.save(user);
            } else {
                throw new OldPasswordException();
            }
        } else {
            throw new ValidatorException();
        }
        return user;
    }

    /**
     * 按用户名查询
     *
     * @param username 用户名
     * @return 用户
     */
    @Override
    public SysMember findByUsername(String username) {
        String key = String.format("username:%s", username);
        SysMember member = getCache(key);
        if (null == member) {
            member = baseDao.findByUsername(username);
            saveCache(key, member);
        }
        return member;
    }

    @Override
    public SysMember findByMobile(String mobile) {
        try {
            Assert.mobile(mobile);
        } catch (IllegalArgumentException e) {
            throw new MobileException();
        }
        String key = String.format("mobile:%s", mobile);
        SysMember member = getCache(key);
        if (null == member) {
            member = baseDao.findByMobile(mobile);
            saveCache(key, member);
        }
        return member;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateMobile(Long uid, String mobile) {
        baseDao.updateMobile(uid, mobile);
    }

    @Override
    public SysMember findByEmail(String email) {
        String key = String.format("email:%s", email);
        SysMember member = getCache(key);
        if (null == member) {
            member = baseDao.findByEmail(email);
            saveCache(key, member);
        }
        return member;
    }

    @Override
    public SysMember findByUsernameAndMobile(String username, String mobile) {
        String key = String.format("%s%s", username, mobile);
        SysMember member = getCache(key);
        if (null == member) {
            member = baseDao.findByUsernameAndMobile(username, mobile);
            saveCache(key, member);
        }
        return member;
    }

    @Override
    public SysMember findByUsernameOrMobile(String username, String mobile) {
        String key;
        if (StringUtils.isNotEmpty(mobile)) {
            key = String.format("mobile:%s", mobile);
        } else if (StringUtils.isNotEmpty(username)) {
            key = String.format("username:%s", username);
        } else {
            return null;
        }
        SysMember member = getCache(key);
        if (null == member) {
            member = baseDao.findByUsernameOrMobile(username, mobile);
            saveCache(key, member);
        }
        return member;
    }

    @Override
    public SysMember findShortInfo(Long id) {
        if (null != id) {
            SysMember member = getCache(id.toString());
            if (null == member) {
                member = baseDao.findShortInfo(id);
                saveCache(id.toString(), member);
            }
            return member;
        }
        return null;
    }

    @Override
    public UserLoginResponse login(UserLoginRequest request) {
        log.debug("login request:" + request);
        UserLoginResponse response = new UserLoginResponse();
        SysSiteConfig siteConfig = getSite();
        try {
            beforeValidate(request);
            SysMember user = null;
            //如果系统配置中开启了短信验证码登录,或者登录类型为[用户名和密码与短信验证码]或者[手机号和短信验证码]
            if (siteConfig.isMemberSmsCodeLogin() && !Objects.equals(EMemberLoginType.usernameAndPwd, siteConfig.getMemberLoginType())) {
                //手机号短信验证码登录验证
                response = mobileLogin(request, response);
                SysMemberDto userInfo = response.getUserInfo();
                if (null == userInfo) {
                    return response;
                }
            } else {
                //如果手机号没有登录成功
                if (StringUtils.isNotEmpty(request.getUserName())) {
                    user = findByUsername(request.getUserName());
                }
                if (null == user && StringUtils.isNotEmpty(request.getMobile())) {
                    if (Assert.validationMobile(request.getMobile(), response)) {
                        return response;
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("用户信息不存在:{}", request.getUserName());
                    }
                    user = findByMobile(request.getMobile());
                    if (null == user) {
                        if (log.isDebugEnabled()) {
                            log.debug("用户信息不存在:{}", request.getMobile());
                        }
                        return new UserLoginResponse(HttpCode.LOGIN_FAIL.value(), HttpCode.LOGIN_FAIL.message());
                    }
                }
                if (null != user) {
                    //如果不是手机号和短信验证码登录,在这里要验证密码
                    if (!Objects.equals(EMemberLoginType.mobileAndSms, siteConfig.getMemberLoginType())) {
                        if (!user.getPassword().equals(encodePassword(request.getPassword()))) {
                            log.debug("用户密码错误");
                            return new UserLoginResponse(HttpCode.LOGIN_FAIL.value(), HttpCode.LOGIN_FAIL.message());
                        }
                    }
                    user.setLoginType(request.getType());
                } else {
                    log.debug("用户信息不存在");
                    return new UserLoginResponse(HttpCode.LOGIN_FAIL.value(), HttpCode.LOGIN_FAIL.message());
                }
            }
            //登录成功,返回结果
            return setMemberToken(response, user);
        } catch (Exception e) {
            log.error("login occur exception :" + e);
            ServiceException serviceException = (ServiceException) ExceptionUtil.handlerExceptionForBiz(e);
            return new UserLoginResponse(serviceException.getCode(), serviceException.getMessage());
        } finally {
            log.debug("login response->" + response);
            if (log.isDebugEnabled()) {
                log.debug("在 debug 模式下不删除验证码缓存.");
            } else {
                if (StringUtils.isNotEmpty(request.getMobile())) {
                    smsService.deleteSmsCodeForCache(smsService.getLoginCodeKey(request.getMobile()));
                }
            }
        }
    }

    @Override
    public UserLoginResponse mobileLogin(UserLoginRequest request, UserLoginResponse response) {
        if (StringUtils.isNotEmpty(request.getMobile())) {
            SysSiteConfig siteConfig = getSite();
            //验证是否是手机号
            if (Assert.validationMobile(request.getMobile(), response)) {
                log.debug("手机验证:{}", response.getResMessage());
                return response;
            }
            //未开启无需验证码手机号测登录试,则需要检查手机验证码
            if (StringUtils.isEmpty(siteConfig.getMemberDebugMobile()) && StringUtils.isEmpty(request.getCode())) {
                log.debug("手机验证码为空:{}", request.getCode());
                return new UserLoginResponse(HttpCode.CODE_IS_NULL.value(), HttpCode.CODE_IS_NULL.message());
            }
            //如果是测试手机号就不需要验证短信验证码
            if (StringUtils.isEmpty(request.getCode()) && StringUtils.isNotEmpty(siteConfig.getMemberDebugMobile()) && !Objects.equals(siteConfig.getMemberDebugMobile(), request.getMobile())
            ) {
                log.debug("手机验证码为空:{}", request.getCode());
                return new UserLoginResponse(HttpCode.CODE_IS_NULL.value(), HttpCode.CODE_IS_NULL.message());
            }
            //按手机号查找用户
            SysMember user = findByMobile(request.getMobile());
            //没有找到用户
            if (null == user) {
                //并且系统开启了用户不存在就直接注册
                if (siteConfig.isMemberLoginNotRegister()) {
                    //如果是测试手机号就不需要验证短信验证码
                    if (validateSmsCode(request, siteConfig)) {
                        return new UserLoginResponse(HttpCode.CODE_FAIL.value(), HttpCode.CODE_FAIL.message());
                    }
                    UserRegisterRequestDto registerRequest = new UserRegisterRequestDto();
                    registerRequest.setMobile(request.getMobile());
                    registerRequest.setUsername(request.getUserName());
                    if (StringUtils.isEmpty(request.getUserName())) {
                        registerRequest.setUsername(request.getMobile());
                    }
                    registerRequest.setPassword(request.getPassword());
                    //系统配置中开启了‘如果登录用户不存在就注册’
                    user = registerMember(registerRequest);
                    SysMemberDto userInfo = bean2Dto(user, SysMemberDto.class);
                    response.setUserInfo(userInfo);
                    return response;
                } else {
                    log.debug("用户信息不存在:{}", request.getMobile());
                    return new UserLoginResponse(HttpCode.LOGIN_FAIL.value(), HttpCode.LOGIN_FAIL.message());
                }
            } else {
                //未开启无需验证码手机号测登录试,则需要检查手机验证码
                if (validateSmsCode(request, siteConfig)) {
                    return new UserLoginResponse(HttpCode.CODE_FAIL.value(), HttpCode.CODE_FAIL.message());
                }
                //登录成功,返回结果
                SysMemberDto userInfo = bean2Dto(user, SysMemberDto.class);
                response.setUserInfo(userInfo);
                return response;
            }
        } else {
            log.debug("手机验证:{}", HttpCode.MOBILE_IS_NULL.message());
            return new UserLoginResponse(HttpCode.MOBILE_IS_NULL.value(), HttpCode.MOBILE_IS_NULL.message());
        }
    }

    /**
     * 验证手机号和短信验证码
     *
     * @param request    请求参数
     * @param siteConfig 系统配置参数
     * @return 验证结果
     */
    private boolean validateSmsCode(UserLoginRequest request, SysSiteConfig siteConfig) {
        if (StringUtils.isEmpty(siteConfig.getMemberDebugMobile()) || !Objects.equals(siteConfig.getMemberDebugMobile(), request.getMobile())) {
            //查找缓存验证码
            String smsCodeForCache = smsService.getSmsCodeForCache(smsService.getLoginCodeKey(request.getMobile()));
            if (!request.getCode().equalsIgnoreCase(smsCodeForCache)) {
                log.debug("手机验证码错误 in code:{},cache code:{}", request.getCode(), smsCodeForCache);
                return true;
            } else {
                log.debug("手机验证码错误 in code:{},cache code:{}", request.getCode(), smsCodeForCache);
                return false;
            }
        }
        return false;
    }

    @Override
    public UserLoginResponse setMemberToken(UserLoginResponse response,
                                            SysMember member) {
        SysMemberDto userInfo = response.getUserInfo();
        Map<String, Object> map = new HashMap<>(12);
        if (null != member) {
            userInfo = bean2Dto(member, SysMemberDto.class);
        }
        Long uid = null;
        if (null != userInfo) {
            uid = userInfo.getId();
            String userName = userInfo.getUsername();
            SysMemberThirdPartyAuth oauth = userInfo.getLoginOauth();
            map.put(UID, uid);
            if (StringUtils.isNotEmpty(userName)) {
                map.put(ACCOUNT, userName);
            }
            if (null != oauth) {
                if (StringUtils.isNotEmpty(oauth.getOpenId())) {
                    map.put(OPEN_ID, oauth.getOpenId());
                }
            }
        }
        String exp = "exp";
        SysSiteConfig siteConfig = getSite();
        long expTime = LocalDateTime.now().plusSeconds(siteConfig.getMemberTokenTime()).toInstant(ZoneOffset.UTC).toEpochMilli();
        map.put(exp, expTime);
        response.setToken(JwtTokenUtil.createToken(map));
        response.setUserInfo(userInfo);
        response.setResCode(HttpCode.LOGIN_SUCCESS.value());
        response.setResMessage(HttpCode.LOGIN_SUCCESS.message());
        WebUtil.setSessionAttribute(CURRENT_USER, userInfo);
        WebUtil.setSessionAttribute(CURRENT_USER_ID, uid);
        return response;
    }

    /**
     * 按手机号修改用户密码
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserRegisterResponse resetPwd(UserRegisterRequestDto request) {
        UserRegisterResponse response = new UserRegisterResponse();
        if (StringUtils.isNotEmpty(request.getMobile())) {
            if (StringUtils.isEmpty(request.getCode())) {
                return new UserRegisterResponse(HttpCode.CODE_IS_NULL.value(), HttpCode.CODE_IS_NULL.message());
            }
            if (Assert.validationMobile(request.getMobile(), response)) {
                return response;
            }
            String smsCodeForCache = smsService.getSmsCodeForCache(smsService.getLoginCodeKey(request.getMobile()));
            if (!request.getCode().equalsIgnoreCase(smsCodeForCache)) {
                return new UserRegisterResponse(HttpCode.CODE_FAIL.value(), HttpCode.CODE_FAIL.message());
            }
            SysMember member = findByMobile(request.getMobile());
            if (null != member) {
                member.setPassword(encodePassword(request.getPassword()));
                this.save(member);
                smsService.deleteSmsCodeForCache(smsService.getResetPwdCodeKey(request.getMobile()));
                return new UserRegisterResponse(HttpCode.OK.value(), HttpCode.OK.message());
            } else {
                return new UserRegisterResponse(HttpCode.NOT_ACCOUNT.value(), HttpCode.NOT_ACCOUNT.message());
            }
        } else {
            return new UserRegisterResponse(HttpCode.MOBILE_IS_NULL.value(), HttpCode.MOBILE_IS_NULL.message());
        }
    }

    @Override
    public void expireToken(String token) {
        log.debug("方法未实现 expireToken: {}", token);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserRegisterResponse register(UserRegisterRequestDto request) {
        log.debug("begin UserCoreService.register,request:【" + request + "】");
        UserRegisterResponse response = new UserRegisterResponse();
        SysSiteConfig siteConfig = getSite();
        try {
            beforeRegisterValidate(request);
            //如果手机号和账号同时为空,返回数据验证失败.
            if (StringUtils.isEmpty(request.getUsername()) && StringUtils.isEmpty(request.getMobile())) {
                throw new ValidatorException();
            }
            if (siteConfig.isMemberSmsCodeRegister()) {
                //如果手机号不为空验证码为空,返回验证码为空
                if (StringUtils.isEmpty(request.getCode()) && StringUtils.isNotEmpty(request.getMobile())) {
                    return new UserRegisterResponse(HttpCode.CODE_IS_NULL.value(), HttpCode.CODE_IS_NULL.message());
                }
                //验证手机号
                if (Assert.validationMobile(request.getMobile(), response)) {
                    return response;
                }
                //查询缓存验证码
                String smsCodeForCache = smsService.getSmsCodeForCache(smsService.getRegisterCodeKey(request.getMobile()));
                //对比验证码
                if (!request.getCode().equalsIgnoreCase(smsCodeForCache)) {
                    return new UserRegisterResponse(HttpCode.CODE_FAIL.value(), HttpCode.CODE_FAIL.message());
                }
            }
            this.registerMember(request);
            return new UserRegisterResponse(HttpCode.OK.value(), HttpCode.OK.message());
        } catch (DuplicateKeyException e) {
            log.error(e.getMessage());
            return new UserRegisterResponse(HttpCode.ACCOUNT_EXISTS.value(), HttpCode.ACCOUNT_EXISTS.message());
        } catch (Exception e) {
            ServiceException serviceException = (ServiceException) ExceptionUtil.handlerExceptionForBiz(e);
            log.error(e.getMessage());
            return new UserRegisterResponse(serviceException.getCode(), serviceException.getMessage());
        } finally {
            log.debug("register response:【" + response + "】");
            if (siteConfig.isMemberSmsCodeRegister() && StringUtils.isNotEmpty(request.getMobile())) {
                //清除验证码
                smsService.deleteSmsCodeForCache(smsService.getRegisterCodeKey(request.getMobile()));
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMember registerMember(UserRegisterRequestDto request) {
        SysMember member = new SysMember();
        member.setUsername(request.getUsername());
        try {
            Assert.mobile(request.getMobile());
        } catch (IllegalArgumentException e) {
            throw new MobileException();
        }
        SysMember sysMember = findByMobile(request.getMobile());
        if (sysMember == null) {
            String pwd;
            if (StringUtils.isEmpty(request.getPassword())) {
                if (StringUtils.isNotEmpty(request.getMobile())) {
                    pwd = request.getMobile();
                } else {
                    SysSiteConfig siteConfig = getSite();
                    pwd = siteConfig.getMemberDefPassword();
                }
            } else {
                pwd = request.getPassword();
            }
            member.setPassword(pwd);
            member.setMobile(request.getMobile());
            member.setSex(request.getSex());
            member.setStatus(1);
            member.setUsername(request.getUsername());
            return this.save(member);
        } else {
            throw new UserAlreadyExistsException();
        }
    }

    @Override
    public CheckAuthorizationResponse validToken(CheckAuthorizationRequest request) {
        CheckAuthorizationResponse response = new CheckAuthorizationResponse();
        try {
            beforeValidateAuth(request);
            Claims claims = JwtTokenUtil.phaseToken(request.getToken());
            Long uid = claims.get(UID, Long.class);
            //如果存在uid才表示为一个正常的token
            if (null != uid && uid > 0) {
                response.setUid(uid);
                String account = claims.get(ACCOUNT, String.class);
                if (StringUtils.isNotEmpty(account)) {
                    response.setAccount(account);
                }
                String openId = claims.get(OPEN_ID, String.class);
                if (StringUtils.isNotEmpty(openId)) {
                    response.setOpenId(openId);
                }
                response.setResCode(HttpCode.OK.value());
                response.setResMessage(HttpCode.OK.message());
            } else {
                response.setResCode(HttpCode.TOKEN_EXPIRED.value());
                response.setResMessage(HttpCode.TOKEN_EXPIRED.message());
            }
        } catch (ExpiredJwtException e) {
            log.error("token expire :{}", e.getMessage());
            response.setResCode(HttpCode.TOKEN_EXPIRED.value());
            response.setResMessage(HttpCode.TOKEN_EXPIRED.message());
        } catch (SignatureException e) {
            log.error("SignatureException :{}", e.getMessage());
            response.setResCode(HttpCode.SIGN_ERROR.value());
            response.setResMessage(HttpCode.SIGN_ERROR.message());
        } catch (Exception e) {
            log.error("login exception :{}", e.getMessage());
            ServiceException serviceException = (ServiceException) ExceptionUtil.handlerExceptionForBiz(e);
            response.setResCode(serviceException.getCode());
            response.setResMessage(serviceException.getMessage());
        } finally {
            log.debug("response:" + response);
        }

        return response;
    }

    private void beforeRegisterValidate(UserRegisterRequestDto request) {
        if (null == request) {
            throw new ValidatorException(Resources.getMessage("ILLEGAL_ACTION"));
        }
    }

    private void beforeValidateAuth(CheckAuthorizationRequest request) {
        if (request == null) {
            throw new LoginException();
        }
        if (StringUtils.isEmpty(request.getToken())) {
            throw new TokenException();
        }
    }

    private void beforeValidate(UserLoginRequest request) {
        if (request == null) {
            throw new ValidatorException("非法请求");
        }
        if (StringUtils.isEmpty(request.getUserName()) && StringUtils.isEmpty(request.getMobile())) {
            throw new ValidatorException("账号为空");
        }

        if (StringUtils.isNotEmpty(request.getMobile())) {
            if (StringUtils.isEmpty(request.getCode())) {
                throw new ValidatorException("验证码为空");
            }
        }

        if (StringUtils.isNotEmpty(request.getUserName())) {
            if (StringUtils.isEmpty(request.getPassword())) {
                throw new ValidatorException("密码为空");
            }
        }
    }

    @Override
    public SysMemberConfig getMemberConfig() {
        return configService.getConfigObject(SysMemberConfig.MEMBER_CONFIG_CONFIG_KEY, SysMemberConfig.class);
    }

    @Override
    public void setMemberConfig(SysMemberConfig config) {
        configService.save(new SysConfig(SysMemberConfig.MEMBER_CONFIG_CONFIG_KEY, new Gson().toJson(config)));
    }
}
